package junior.db.query;

import junior.db.dao.base.AbstractFilter;
import junior.db.dao.enums.LikeMode;
import junior.util.BeanMapper;
import lombok.Builder;
import org.apache.commons.lang3.StringUtils;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Predicate;

/**
 * @author wubo
 * @date 2024/9/6 10:48
 */
@Builder
public class DefaultFilterExampleApplier implements FilterExampleApplier {
    private static Predicate<String> defaultAutoBeginDateFilterPredicate = field -> startsWith(field, "begin", "start") && endsWith(field, "date", "time");
    private static Predicate<String> defaultAutoEndDateFilterPredicate = field -> startsWith(field, "end") && endsWith(field, "date", "time");
    private static Predicate<String> defaultAutoLikeFilterPredicate = field -> endsWith(field, "name");
    private static LikeMode defaultLikeMode = LikeMode.Prefix;
    
    public static void setDefaultAutoBeginDateFilterPredicate(Predicate<String> defaultAutoBeginDateFilterPredicate) {
        DefaultFilterExampleApplier.defaultAutoBeginDateFilterPredicate = defaultAutoBeginDateFilterPredicate;
    }
    
    public static void setDefaultAutoEndDateFilterPredicate(Predicate<String> defaultAutoEndDateFilterPredicate) {
        DefaultFilterExampleApplier.defaultAutoEndDateFilterPredicate = defaultAutoEndDateFilterPredicate;
    }
    
    public static void setDefaultAutoLikeFilterPredicate(Predicate<String> defaultAutoLikeFilterPredicate) {
        DefaultFilterExampleApplier.defaultAutoLikeFilterPredicate = defaultAutoLikeFilterPredicate;
    }
    
    public static void setDefaultLikeMode(LikeMode defaultLikeMode) {
        DefaultFilterExampleApplier.defaultLikeMode = defaultLikeMode;
    }
    
    private String[] includeKeys;
    private String[] excludeKeys;
    private Map<String, String> aliasMap;
    private boolean skipNullValue;
    
    private Map<String, Consumer<AbstractFilter<?>>> filterCustomerAdapters;
    
    private Predicate<String> autoBeginDateFilterPredicate = defaultAutoBeginDateFilterPredicate;
    private Predicate<String> autoEndDateFilterPredicate = defaultAutoEndDateFilterPredicate;
    private Predicate<String> autoLikeFilterPredicate = defaultAutoLikeFilterPredicate;
    private LikeMode likeMode = defaultLikeMode;
    
    @Override
    public void apply(Object example, AbstractFilter<?> filter) {
        BeanMapper beanMapper = new BeanMapper();
        if (skipNullValue) {
            beanMapper.skipNullValue();
        }
        if (includeKeys != null) {
            beanMapper.includeProperties(includeKeys);
        }
        if (excludeKeys != null) {
            beanMapper.excludeProperties(excludeKeys);
        }
        Map<String, Object> map = new HashMap<>();
        if (!(example instanceof Map)) {
            beanMapper.copyBeanToMap(example, map);
        } else {
            beanMapper.copyMapToMap((Map) example, map);
        }
        doApplyMap(map, filter);
    }
    
    private void doApplyMap(Map<String, Object> map, AbstractFilter<?> filter) {
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            String key = entry.getKey();
            if (filterCustomerAdapters != null && filterCustomerAdapters.containsKey(key)) {
                filterCustomerAdapters.get(key).accept(filter);
            }
            String field = aliasMap != null && aliasMap.containsKey(key) ? aliasMap.get(key) : key;
            if (autoBeginDateFilterPredicate != null && autoBeginDateFilterPredicate.test(key)) {
                filter.gte(field, entry.getValue());
                continue;
            }
            if (autoEndDateFilterPredicate != null && autoEndDateFilterPredicate.test(key)) {
                filter.lte(field, entry.getValue());
                continue;
            }
            if (autoLikeFilterPredicate != null && autoLikeFilterPredicate.test(key)) {
                filter.like(field, entry.getValue(), likeMode);
                continue;
            }
            filter.eq(field, entry.getValue());
        }
    }
    
    protected static boolean startsWith(String target, String... strings) {
        return Arrays.stream(strings).anyMatch(s -> target.startsWith(s) || target.startsWith(StringUtils.capitalize(s)));
    }
    
    protected static boolean endsWith(String target, String... strings) {
        return Arrays.stream(strings).anyMatch(s -> target.endsWith(s) || target.endsWith(StringUtils.capitalize(s)));
    }
}
